#include <stdio.h>
#include <string.h>
#include "BSP/InternalFlash/bsp_internal_flash.h"

/**
 * @brief  读取指定地址的半字(16位) 数据
 * @note   32位单片机的话，一个字是32位，半字是16位
 * @param  faddr 要读取的地址，32位地址(此地址必须为2的倍数!!)
 * @retval 返回一个16位数据
 */
uint16_t InternalFLASH_ReadHalfWord(uint32_t faddr) { 
  return *(__IO uint16_t *)faddr; 
}

#if InternalFLASH_WREN // 如果使能了写

/**
 * @brief  不检查是否擦除，直接写入半字数据
 * @note   注意：数据个数按半字(16位)计算
 * @param  WriteAddr 起始地址
 * @param  pBuffer 数据指针
 * @param  NumToWrite 要写入的数据的半字(16位)数，字节数x2
 * @retval
 */
void InternalFLASH_Write_NoCheck(uint32_t WriteAddr, uint16_t *pBuffer, uint16_t NumToWrite) {
  uint16_t i;
  for (i = 0; i < NumToWrite; i++)
  {
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, WriteAddr, pBuffer[i]);
    WriteAddr += 2; // 地址增加2，每次写入16位数据
  }
}

uint16_t InternalFLASH_BUF[InternalFLASH_SECTOR_SIZE / 2]; // 最多是2K字节,每个数据是16位，所以大小定义为扇区的一半
/**
 * @brief  从指定地址开始写入指定长度的数据
 * @note   注意：数据个数按半字(16位)计算
 * @param  WriteAddr 起始地址(此地址必须为2的倍数!!)
 * @param  pBuffer 数据指针
 * @param  NumToWrite 要写入的半字(16位)数(就是要写入的16位数据的个数)
 * @retval
 */
void InternalFLASH_Write(uint32_t WriteAddr, uint16_t *pBuffer, uint16_t NumToWrite) {
  uint32_t secpos;    // 扇区地址
  uint16_t secoff;    // 扇区内偏移地址(16位字计算)
  uint16_t secremain; // 扇区内剩余地址(16位字计算)
  uint16_t i;
  uint32_t offaddr; // 去掉 0X08000000 后的地址，也就是相对于 0X08000000 的偏移地址

  if (WriteAddr < InternalFLASH_BASE || (WriteAddr >= (InternalFLASH_BASE + 1024 * InternalFLASH_SIZE)))
    return; // 非法地址

  HAL_FLASH_Unlock();                       // 解锁
  offaddr = WriteAddr - InternalFLASH_BASE; // 计算实际偏移地址，要写入的地址 - 0x0800 0000
  secpos = offaddr / InternalFLASH_SECTOR_SIZE; // 计算要写的地址是哪个扇区，也就是页，偏移地址/2048即可。扇区地址 0~255
                                                // for STM32F103ZET6
  secoff = (offaddr % InternalFLASH_SECTOR_SIZE) / 2; // 在扇区内的偏移(2个字节为基本单位.)
  secremain = InternalFLASH_SECTOR_SIZE / 2 - secoff; // 计算扇区剩余空间大小
  if (NumToWrite <= secremain)                        // 判断整个页中剩下的空间是否足够写入我们要写入的数据
    secremain = NumToWrite; // 要写的数据长度不大于该扇区范围的话，我们将剩余长度设置为要写入的长度
  while (1)
  {
    InternalFLASH_Read(secpos * InternalFLASH_SECTOR_SIZE + InternalFLASH_BASE, InternalFLASH_BUF,
                       InternalFLASH_SECTOR_SIZE / 2); // 读出整个扇区的内容
    for (i = 0; i < secremain; i++) // 校验数据，判断我们要写的区域中的数据是否都是0xFFFF // 校验数据
    {
      if (InternalFLASH_BUF[secoff + i] != 0XFFFF)
        break; // 需要擦除
    }
    if (i < secremain) // 需要擦除
    {
      // 这里的擦除操作也可以使用 HAL_FLASHEx_Erase() 函数，它包含了下边的三个步骤
      FLASH_PageErase(secpos * InternalFLASH_SECTOR_SIZE + InternalFLASH_BASE); // 擦除这个扇区
      FLASH_WaitForLastOperation(FLASH_WAITETIME);                              // 等待上次操作完成
      CLEAR_BIT(FLASH->CR, FLASH_CR_PER); // 清除CR寄存器的PER位，此操作应该在FLASH_PageErase()中完成！
                                          // 但是HAL库里面并没有做，因为HAL库一般使用HAL_FLASHEx_Erase()来擦除
      for (i = 0; i < secremain; i++)     /// 复制要写入的数据到缓冲区
      {
        InternalFLASH_BUF[i + secoff] = pBuffer[i]; // 直接从偏移的位置复制数据，保护了其他不需要被覆盖的数据
      }
      InternalFLASH_Write_NoCheck(secpos * InternalFLASH_SECTOR_SIZE + InternalFLASH_BASE, InternalFLASH_BUF,
                                  InternalFLASH_SECTOR_SIZE / 2); // 写入整个扇区
    }
    else
    {
      FLASH_WaitForLastOperation(FLASH_WAITETIME);                // 等待上次操作完成
      InternalFLASH_Write_NoCheck(WriteAddr, pBuffer, secremain); // 写已经擦除了的,直接写入扇区剩余区间.
    }
    if (NumToWrite == secremain)
      break; // 写入结束了
    else     // 写入未结束
    {
      secpos++;                   // 扇区地址增1
      secoff = 0;                 // 偏移位置为0
      pBuffer += secremain;       // 指针偏移
      WriteAddr += secremain * 2; // 写地址偏移(16位数据地址,需要*2)
      NumToWrite -= secremain;    // 字节(16位)数递减
      if (NumToWrite > (InternalFLASH_SECTOR_SIZE / 2))
        secremain = InternalFLASH_SECTOR_SIZE / 2; // 下一个扇区还是写不完
      else
        secremain = NumToWrite; // 下一个扇区可以写完了
    }
  }
  HAL_FLASH_Lock(); // 上锁
}
#endif

/**
 * @brief  从指定地址开始读出指定长度的数据
 * @note   注意：数据个数按半字(16位)计算
 * @param  ReadAddr 起始地址
 * @param  pBuffer 数据指针
 * @param  NumTRead 要读取的半字(16位)数,即2个字节的整数倍
 * @retval
 */
void InternalFLASH_Read(uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead) {
  uint16_t i;
  for (i = 0; i < NumToRead; i++)
  {
    pBuffer[i] = InternalFLASH_ReadHalfWord(ReadAddr); // 读取2个字节.
    ReadAddr += 2;                                     // 偏移2个字节.
  }
}

void InternalFlash_Test(void) {
  static int cnt = 0;
  uint8_t    TEXT_Buffer[128] = {0};

  uint16_t datatemp[64] = {0};
  uint32_t FLASH_SAVE_ADDR = 0X08070000;

  // 生成要写入的数据
  cnt++;
  snprintf((char *)TEXT_Buffer, sizeof(TEXT_Buffer), "%s %d", "STM32F103 FLASH TEST!", cnt);
  printf("TEXT_Buffer:%s, Size=%d HalfWordSize=%d\r\n", TEXT_Buffer, sizeof(TEXT_Buffer),
         sizeof(TEXT_Buffer) / 2 + ((sizeof(TEXT_Buffer) % 2) ? 1 : 0));

  // 写入前读取一次
  InternalFLASH_Read(FLASH_SAVE_ADDR, (uint16_t *)datatemp,
                     sizeof(TEXT_Buffer) / 2 + ((sizeof(TEXT_Buffer) % 2) ? 1 : 0));
  printf("before write:%s\r\n", (char *)datatemp);

  // 写入数据
  InternalFLASH_Write(FLASH_SAVE_ADDR, (uint16_t *)TEXT_Buffer,
                      sizeof(TEXT_Buffer) / 2 + ((sizeof(TEXT_Buffer) % 2) ? 1 : 0));

  memset(datatemp, 0, sizeof(datatemp));
  InternalFLASH_Read(FLASH_SAVE_ADDR, (uint16_t *)datatemp,
                     sizeof(TEXT_Buffer) / 2 + ((sizeof(TEXT_Buffer) % 2) ? 1 : 0));

  printf("after write:%s\r\n", (char *)datatemp);
  return;

}
